#include "Winsock2ClientSocket.h"

int net::Winsock2ClientSocket::WSA_VERSION = 2;
int net::Winsock2ClientSocket::WSA_REVISION = 2;

net::Winsock2ClientSocket::Winsock2ClientSocket(string serverHost, string serverPort)
{
	WSADATA wsaData;
    int startUpResult = WSAStartup(MAKEWORD(WSA_VERSION, WSA_REVISION), &wsaData);
    if (startUpResult != 0 ||
		(LOBYTE(wsaData.wVersion) != WSA_VERSION || HIBYTE(wsaData.wVersion) != WSA_REVISION))
    {
        //cout << "WSAStartup failed with error: " << iResult << endl;
    }

	obtainedAddress = NULL;
    memset(&requestedAddress, 0, sizeof(requestedAddress));
    requestedAddress.ai_family = AF_UNSPEC;
    requestedAddress.ai_socktype = SOCK_STREAM;
    requestedAddress.ai_protocol = IPPROTO_TCP;

    int addressResult = getaddrinfo(serverHost.c_str(), serverPort.c_str(), &requestedAddress, &obtainedAddress);
    if (addressResult != 0)
    {
        //cout << "getaddrinfo failed with error: " << iResult << endl;
        WSACleanup();
    }
	socketConnection = INVALID_SOCKET;
}

net::Winsock2ClientSocket::~Winsock2ClientSocket(void)
{
	WSACleanup();
}

void net::Winsock2ClientSocket::doConnect(void) throw (NetworkException)
{
	socketConnection = INVALID_SOCKET;
	struct addrinfo* ptr = NULL;
	int iResult = 0;
	for (ptr=obtainedAddress; ptr!=NULL; ptr=ptr->ai_next)
    {
        socketConnection = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
        if (socketConnection == INVALID_SOCKET)
        {
            cout << "socket() failed with error: " << WSAGetLastError() << endl;
        }
		
        iResult = connect(socketConnection, ptr->ai_addr, (int)ptr->ai_addrlen);
        if (iResult == SOCKET_ERROR)
        {
            cout << "connect() failed with error: " << WSAGetLastError() << endl;
			closesocket(socketConnection);
            socketConnection = INVALID_SOCKET;
            continue;
        }
        break;
    }
	if (socketConnection == INVALID_SOCKET)
	{
		throw NetworkException("The address information retrieved by function getaddrinfo is invalid.");
	}
	freeaddrinfo(obtainedAddress);
}

int net::Winsock2ClientSocket::doSend(const char* sendbuf)
{
	int sendResult = send(socketConnection, sendbuf, (int)strlen(sendbuf), 0);
	if (sendResult == SOCKET_ERROR)
	{
		cout << "send failed with error: " << WSAGetLastError() << endl;
		closesocket(socketConnection);
		return 1;
	}
	return sendResult;
	/*
	string str = sendbuf;
	int totalBytesToSend = str.length();
	int bytesSend = 0;
	const char* data = str.data();
	send(socketConnection, (const char*)&totalBytesToSend, sizeof(int), 0);
	while (bytesSend < totalBytesToSend)
	{
		int sendResult = send(socketConnection, (data+bytesSend), totalBytesToSend, 0);
		if (sendResult == SOCKET_ERROR)
		{
			cout << "[WINSOCKCLIENT] Send failed with error: " << WSAGetLastError() << endl;
			closesocket(socketConnection);
			//WSACleanup();
			bytesSend = -1;
			break;
		}
		else
		{
			bytesSend += sendResult;
			totalBytesToSend -= sendResult;
		}
	}
	return bytesSend;
	*/
}

int net::Winsock2ClientSocket::doReceive(char* recvbuf, int recvbuflen)
{
	return recv(socketConnection, recvbuf, recvbuflen, 0);
	/*
	int totalBytesToRecieve = 0;
	int bytesRecieved = 0;
	recv(socketConnection, (char*)&totalBytesToRecieve, sizeof(int), 0);
	while (bytesRecieved < totalBytesToRecieve)
	{
		char recievebufferData[1024];
		int recieveResult = recv(socketConnection, recievebufferData,1024, 0);
		if (recieveResult == SOCKET_ERROR)
		{
			cout << "[WINSOCKCLIENT] Send failed with error: " << WSAGetLastError() << endl;
			closesocket(socketConnection);
			//WSACleanup();
			bytesRecieved = -1;
			break;
		}
		else
		{
			strcat (recvbuf,recievebufferData);
			bytesRecieved += recieveResult;
			totalBytesToRecieve -= recieveResult;
		}
	}
	return bytesRecieved;
	*/
}

void net::Winsock2ClientSocket::doShutdown(void)
{
	int disconnectResult = shutdown(socketConnection, SD_SEND);
    if (disconnectResult == SOCKET_ERROR)
    {
        cout << "shutdown failed with error: " << WSAGetLastError() << endl;
        closesocket(socketConnection);
    }
}

void net::Winsock2ClientSocket::doClose(void)
{
	closesocket(socketConnection);
}

string net::Winsock2ClientSocket::toString(void)
{
	int i = 1;
	struct addrinfo* ptr = NULL;
	string info = "";
	stringstream ss;
	for(ptr=obtainedAddress; ptr != NULL; ptr=ptr->ai_next)
	{
		ss << ptr->ai_flags;
		ss.str();
		info.append("[Flags= ").append(ss.str());
		ss.clear();
        info.append("|Family= ");
        switch (ptr->ai_family)
		{
            case AF_UNSPEC:
                info.append("Unspecified");
                break;
            case AF_INET:
                info.append("AF_INET (IPv4)");
                break;
            case AF_INET6:
                info.append("AF_INET6 (IPv6)");
                break;
            case AF_NETBIOS:
                info.append("AF_NETBIOS (NetBIOS)");
                break;
            default:
				ss << ptr->ai_family;
				info.append("Other(").append(ss.str()).append(")");
				ss.clear();
                break;
        }
        info.append("|Socket type=");
        switch (ptr->ai_socktype) {
            case 0:
                info.append("Unspecified");
                break;
            case SOCK_STREAM:
                info.append("SOCK_STREAM (stream)");
                break;
            case SOCK_DGRAM:
                info.append("SOCK_DGRAM (datagram)");
                break;
            case SOCK_RAW:
                info.append("SOCK_RAW (raw)");
                break;
            case SOCK_RDM:
                info.append("SOCK_RDM (reliable message datagram)");
                break;
            case SOCK_SEQPACKET:
                info.append("SOCK_SEQPACKET (pseudo-stream packet)");
                break;
            default:
				ss << ptr->ai_socktype;
				info.append("Other(").append(ss.str()).append(")");
				ss.clear();
                break;
        }
        info.append("|Protocol=");
        switch (ptr->ai_protocol) {
            case 0:
                info.append("Unspecified");
                break;
            case IPPROTO_TCP:
                info.append("IPPROTO_TCP (TCP)");
                break;
            case IPPROTO_UDP:
                info.append("IPPROTO_UDP (UDP)");
                break;
            default:
				ss << ptr->ai_protocol;
				info.append("Other(").append(ss.str()).append(")");
				ss.clear();
                break;
        }
		ss << ptr->ai_addrlen;
		info.append("|sockaddr length=").append(ss.str());
		ss.clear();
		if (ptr->ai_canonname != 0)
		{
			info.append("|Canonical name= ").append(ptr->ai_canonname);
		}
		info.append("]");
    }
	return info;
}
